<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SHIYI-API响应速度测试</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
    <style>
        :root {
--primary-color: #61677C;
--light-color: #ECF0F3;
--shadow-light: #FFF;
--shadow-dark: #D1D9E6;
--success-color: #5AD25E;
--warning-color: #FFA726;
--error-color: #FF5252;
--info-color: #3498DB;
--neumorphism-radius: 15px;
--transition-base: all 0.3s ease;
        }
 
        body {
            font-family: 'Segoe UI', sans-serif;
            background-color: var(--light-color);
            color: var(--primary-color);
            margin: 0;
            padding: 0;
        }
 
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 30px;
        }
 
        .header {
            text-align: center;
            margin-bottom: 30px;
            padding: 20px;
            border-radius: var(--neumorphism-radius);
            background: var(--light-color);
            box-shadow: -5px -5px 15px var(--shadow-light),
                        5px 5px 15px var(--shadow-dark);
        }
         .footer {
        text-align: center;
        padding: 20px;
        color: #666;
        font-size: 0.85rem;
    }
 
    .footer p {
        margin-bottom: 8px;
    }
 
        .title {
            font-size: 2.2rem;
            font-weight: 700;
            margin-bottom: 10px;
            color: var(--primary-color);
        }
 
        .subtitle {
            color: #666;
            font-size: 1rem;
        }
 
        .control-panel {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-bottom: 30px;
        }
 
        .btn {
            padding: 12px 25px;
            border-radius: var(--neumorphism-radius);
            border: none;
            background: var(--light-color);
            box-shadow: -5px -5px 15px var(--shadow-light),
                        5px 5px 15px var(--shadow-dark);
            cursor: pointer;
            transition: var(--transition-base);
            font-weight: 600;
            color: var(--primary-color);
        }
 
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: -8px -8px 20px var(--shadow-light),
                        8px 8px 20px var(--shadow-dark);
        }
 
        .btn:active {
            transform: translateY(0);
            box-shadow: inset 2px 2px 5px var(--shadow-dark),
                        inset -5px -5px 10px var(--shadow-light);
        }
 
        .btn-primary {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            box-shadow: -5px -5px 15px var(--shadow-light),
                        5px 5px 15px var(--shadow-dark);
        }
 
        .api-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
            gap: 20px;
        }
 
        .api-card {
            background: var(--light-color);
            border-radius: var(--neumorphism-radius);
            box-shadow: -5px -5px 15px var(--shadow-light),
                        5px 5px 15px var(--shadow-dark);
            padding: 20px;
            transition: var(--transition-base);
        }
 
        .api-card:hover {
            transform: translateY(-5px);
            box-shadow: -8px -8px 20px var(--shadow-light),
                        8px 8px 20px var(--shadow-dark);
        }
 
        .card-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
            padding-bottom: 15px;
            border-bottom: 1px solid rgba(0,0,0,0.05);
        }
 
        .card-title {
            font-size: 1.2rem;
            font-weight: 600;
            color: var(--primary-color);
            display: flex;
            align-items: center;
            gap: 8px;
        }
 
        .api-tag {
            font-size: 0.75rem;
            padding: 3px 8px;
            border-radius: 12px;
            font-weight: 600;
        }
 
        .tag-fast {
            background: rgba(90, 210, 94, 0.15);
            color: var(--success-color);
        }
 
        .tag-normal {
            background: rgba(255, 167, 38, 0.15);
            color: var(--warning-color);
        }
 
        .tag-slow {
            background: rgba(255, 82, 82, 0.15);
            color: var(--error-color);
        }
 
        .tag-offline {
            background: rgba(97, 103, 124, 0.15);
            color: var(--primary-color);
        }
 
        .speed-indicator {
            display: flex;
            align-items: center;
            gap: 5px;
        }
 
        .speed-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
        }
 
        .speed-fast {
            background: var(--success-color);
            box-shadow: 0 0 10px var(--success-color);
        }
 
        .speed-medium {
            background: var(--warning-color);
            box-shadow: 0 0 10px var(--warning-color);
        }
 
        .speed-slow {
            background: var(--error-color);
            box-shadow: 0 0 10px var(--error-color);
        }
 
        .card-content {
            margin-bottom: 15px;
            line-height: 1.5;
            color: #666;
        }
 
        .api-url {
            font-size: 0.85rem;
            color: #888;
            word-break: break-all;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
            gap: 5px;
        }
 
        .card-footer {
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
 
        .response-time {
            font-size: 0.9rem;
            color: #888;
        }
 
        .time-value {
            font-weight: 600;
            font-size: 1.1rem;
        }
 
        .btn-test {
            padding: 8px 15px;
            border-radius: 8px;
            background: var(--light-color);
            box-shadow: -3px -3px 8px var(--shadow-light),
                        3px 3px 8px var(--shadow-dark);
            border: none;
            cursor: pointer;
            transition: var(--transition-base);
            font-size: 0.85rem;
            color: var(--primary-color);
        }
 
        .btn-test:hover {
            box-shadow: -2px -2px 5px var(--shadow-light),
                        2px 2px 5px var(--shadow-dark);
        }
 
        .btn-test:active {
            box-shadow: inset 1px 1px 2px var(--shadow-dark),
                        inset -1px -1px 2px var(--shadow-light);
        }
 
        .progress-container {
            margin: 30px auto;
            max-width: 600px;
            text-align: center;
        }
 
        .progress-bar {
            height: 10px;
            background: var(--light-color);
            border-radius: 5px;
            box-shadow: inset 2px 2px 5px var(--shadow-dark),
                        inset -5px -5px 10px var(--shadow-light);
            margin-bottom: 15px;
            overflow: hidden;
            position: relative;
        }
 
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #667eea, #764ba2);
            width: 0;
            transition: width 0.3s ease;
        }
 
        .progress-text {
            font-size: 1rem;
            color: var(--primary-color);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
 
        .progress-count {
            font-weight: bold;
        }
 
        .progress-speed {
            font-size: 0.9rem;
            color: #888;
        }
 
        .status-code {
            font-size: 0.8rem;
            padding: 2px 6px;
            border-radius: 4px;
            margin-left: 5px;
        }
        .status-2xx { background-color: rgba(90, 210, 94, 0.15); color: var(--success-color); }
        .status-4xx { background-color: rgba(255, 167, 38, 0.15); color: var(--warning-color); }
        .status-5xx { background-color: rgba(255, 82, 82, 0.15); color: var(--error-color); }
        .status-timeout { background-color: rgba(97, 103, 124, 0.15); color: var(--primary-color); }
 
        @media (max-width: 768px) {
            .api-grid {
                grid-template-columns: 1fr;
            }
            
            .control-panel {
                flex-direction: column;
                align-items: center;
            }
        }
    </style>
</head>
<body>
    <div id="app" class="container">
        <div class="header">
            <h1 class="title">SHIYI-API响应速度测试🎊</h1>
            <p class="subtitle">实时监测所有API接口的响应时间和状态✨</p>
        </div>
 
        <div class="control-panel">
            <button class="btn btn-primary" @click="startTesting" :disabled="isTesting">
                <i class="fas fa-play"></i> {{ isTesting ? '测试中...' : '开始测试' }}
            </button>
            <button class="btn" @click="stopTesting" :disabled="!isTesting">
                <i class="fas fa-stop"></i> 停止测试
            </button>
        </div>
 
        <div class="progress-container">
            <div class="progress-bar">
                <div class="progress-fill" :style="{ width: progress + '%' }"></div>
            </div>
            <div class="progress-text">
                <span class="progress-count">{{ progressText }}</span>
                <span class="progress-speed" v-if="averageSpeed > 0">平均速度: {{ Math.round(averageSpeed) }}ms</span>
            </div>
        </div>
 
        <div class="api-grid">
            <div v-for="api in apis" :key="api.id" class="api-card">
                <div class="card-header">
                    <div class="card-title">
                        {{ api.name }}
                        <span class="api-tag" :class="getTagClass(api)">{{ getTagText(api) }}</span>
                    </div>
                    <div class="speed-indicator">
                        <div class="speed-dot" :class="getSpeedClass(api)"></div>
                        <span>{{ getStatusText(api) }}</span>
                        <span v-if="api.statusCode" class="status-code" :class="'status-' + getStatusCodeClass(api.statusCode)">
                            {{ api.statusCode }}
                        </span>
                    </div>
                </div>
                
                <div class="card-content">
                    <div class="api-url">
                        <i class="fas fa-link"></i> {{ api.testUrl }}
                    </div>
                    <div>{{ api.desc }}</div>
                </div>
                
                <div class="card-footer">
                    <div class="response-time">
                        最后测试: <span class="time-value">{{ api.lastTest || '--' }}</span>
                    </div>
                    <button class="btn-test" @click="testSingleAPI(api)" :disabled="api.status === 'offline'">
                        <i class="fas fa-sync-alt"></i> 立即测试 
                    </button>
                </div>
            </div>
        </div>
    </div>
 <div class="footer">
        <p>© <?php echo date('Y'); ?> <?php echo $config['title']; ?> -由SHIYI-API强力💪🏻驱动.</p>
        <p>❤️作者：十一</p>
        <p>转载/使用请保留版权🙏🏻</p>
    </div>
    <script>
        new Vue({
            el: '#app',
            data: {
                apis: [],
                isTesting: false,
                progress: 0,
                progressText: '准备就绪，点击开始测试',
                averageSpeed: 0,
                config: {
                    timeout: 3000,      // 超时阈值(ms)
                    fastThreshold: 500, // 快速响应阈值
                    interval: 10000     // 自动测试间隔 
                },
                testInterval: null,
                testStartTime: null,
                websetting: {
                    url: '' // 将从接口获取 
                }
            },
            created() {
                this.fetchWebSetting();
                this.fetchAllAPIs();
            },
            methods: {
                fetchWebSetting() {
                    fetch('Data/api.php?type=getWebSetting')
                        .then(res => res.json())
                        .then(json => {
                            if (json.code === 0) {
                                this.websetting = json.data;
                            }
                        });
                },
                
                fetchAllAPIs() {
                    fetch('Data/api.php?type=getAllApi')
                        .then(res => res.json())
                        .then(json => {
                            if (json.code === 0) {
                                this.apis = json.data.map(api => ({
                                    ...api,
                                    lastTest: null,
                                    responseTime: null,
                                    status: api.status == "1" ? 'pending' : 'offline',
                                    testUrl: `${this.websetting.url}API/${api.enname}.php`,
                                    statusCode: null,
                                    error: null 
                                }));
                                this.checkInitialStatus();
                            }
                        });
                },
 
                checkInitialStatus() {
                    this.apis.forEach(api => {
                        if (api.status !== 'offline') {
                            this.testAPI(api).then(result => {
                                api.status = result.status;
                                api.responseTime = Math.round(result.duration);
                                api.statusCode = result.statusCode;
                                api.error = result.error;
                            });
                        }
                    });
                },
 
                startTesting() {
                    if (this.isTesting) return;
                    
                    this.isTesting = true;
                    this.progressText = '准备开始测试...';
                    this.testStartTime = performance.now();
                    
                    this.runFullTest();
                    
                    this.testInterval = setInterval(() => {
                        this.runFullTest();
                    }, this.config.interval);
                },
 
                stopTesting() {
                    if (!this.isTesting) return;
                    
                    this.isTesting = false;
                    clearInterval(this.testInterval);
                    this.progressText = '测试已停止';
                },
 
                runFullTest() {
                    if (this.apis.length === 0) return;
                    
                    this.progress = 0;
                    this.progressText = `测试中 (0/${this.apis.length})`;
                    this.averageSpeed = 0;
                    let completedCount = 0;
                    let totalResponseTime = 0;
                    
                    this.apis.forEach((api, index) => {
                        if (api.status !== 'offline') {
                            this.testAPI(api).then(result => {
                                Object.assign(api, {
                                    lastTest: new Date().toLocaleTimeString(),
                                    responseTime: Math.round(result.duration),
                                    status: result.status,
                                    statusCode: result.statusCode,
                                    error: result.error
                                });
                                
                                // 计算平均速度 
                                if (result.success) {
                                    completedCount++;
                                    totalResponseTime += result.duration;
                                    this.averageSpeed = totalResponseTime / completedCount;
                                }
                                
                                // 更新进度
                                this.progress = Math.floor((index + 1) / this.apis.length * 100);
                                this.progressText = `测试中 (${index + 1}/${this.apis.length})`;
                                
                                if (index === this.apis.length - 1) {
                                    this.progressText = `测试完成 (${this.apis.length}/${this.apis.length})`;
                                }
                            });
                        }
                    });
                },
 
                testSingleAPI(api) {
                    if (api.status === 'offline') return;
                    
                    api.status = 'testing';
                    const startTime = performance.now();
                    
                    return Promise.race([
                        fetch(api.testUrl),
                        new Promise((_, reject) => 
                            setTimeout(() => reject(new Error('timeout')), this.config.timeout)
                        )
                    ])
                    .then(response => {
                        if (!response.ok) {
                            throw new Error(`HTTP ${response.status}`);
                        }
                        return response.text();
                    })
                    .then(() => {
                        const duration = performance.now() - startTime;
                        Object.assign(api, {
                            lastTest: new Date().toLocaleTimeString(),
                            responseTime: Math.round(duration),
                            status: duration < this.config.fastThreshold ? 'fast' : 'normal',
                            statusCode: '200',
                            error: null 
                        });
                    })
                    .catch(err => {
                        let errorMsg = '请求失败';
                        let statusCode = 'timeout';
                        
                        if (err.message.includes('404')) {
                            errorMsg = '接口不存在';
                            statusCode = '404';
                        } else if (err.message.includes('500')) {
                            errorMsg = '服务器错误';
                            statusCode = '500';
                        }
                        
                        Object.assign(api, {
                            lastTest: new Date().toLocaleTimeString(),
                            responseTime: err.message === 'timeout' ? 
                                Math.round(this.config.timeout) : 
                                Math.round(Math.min(performance.now() - startTime, this.config.timeout)),
                            status: 'timeout',
                            statusCode: statusCode,
                            error: errorMsg
                        });
                    });
                },
 
                testAPI(api) {
                    if (api.status === 'offline') {
                        return Promise.resolve({
                            success: false,
                            duration: 0,
                            status: 'offline',
                            statusCode: 'disabled'
                        });
                    }
 
                    const startTime = performance.now();
                    
                    return Promise.race([
                        fetch(api.testUrl),
                        new Promise((_, reject) => 
                            setTimeout(() => reject(new Error('timeout')), this.config.timeout)
                        )
                    ])
                    .then(response => {
                        const duration = performance.now() - startTime;
                        
                        if (!response.ok) {
                            throw new Error(`HTTP ${response.status}`);
                        }
                        
                        return {
                            success: true,
                            duration: duration,
                            status: duration < this.config.fastThreshold ? 'fast' : 'normal',
                            statusCode: response.status,
                            error: null
                        };
                    })
                    .catch(err => {
                        let statusCode = 'timeout';
                        let errorMsg = '请求超时';
                        
                        if (err.message.includes('404')) {
                            statusCode = '404';
                            errorMsg = '接口不存在';
                        } else if (err.message.includes('500')) {
                            statusCode = '500';
                            errorMsg = '服务器错误';
                        }
                        
                        return {
                            success: false,
                            duration: err.message === 'timeout' ? 
                                this.config.timeout : 
                                Math.min(performance.now() - startTime, this.config.timeout),
                            status: 'timeout',
                            statusCode: statusCode,
                            error: errorMsg
                        };
                    });
                },
 
                getTagClass(api) {
                    if (!api.status) return 'tag-offline';
                    return {
                        fast: 'tag-fast',
                        normal: 'tag-normal',
                        timeout: 'tag-slow',
                        pending: 'tag-normal',
                        offline: 'tag-offline',
                        testing: 'tag-normal'
                    }[api.status] || 'tag-offline';
                },
 
                getTagText(api) {
                    if (!api.status) return '离线';
                    return {
                        fast: '极速',
                        normal: '正常',
                        timeout: '超时',
                        pending: '待测',
                        offline: '已禁用',
                        testing: '测试中'
                    }[api.status] || '离线';
                },
 
                getSpeedClass(api) {
                    if (!api.status) return '';
                    return {
                        fast: 'speed-fast',
                        normal: 'speed-medium',
                        timeout: 'speed-slow',
                        pending: '',
                        offline: '',
                        testing: 'speed-medium'
                    }[api.status] || '';
                },
 
                getStatusText(api) {
                    if (!api.status) return '未测试';
                    if (api.error) return api.error;
                    if (api.status === 'testing') return '测试中...';
                    return api.responseTime ? `${api.responseTime}ms` : '--';
                },
                
                getStatusCodeClass(code) {
                    if (code === 'timeout') return 'timeout';
                    if (code === 'disabled') return 'timeout';
                    if (code >= 200 && code < 300) return '2xx';
                    if (code >= 400 && code < 500) return '4xx';
                    if (code >= 500) return '5xx';
                    return '';
                }
            }
        });
    </script>
</body>
</html>